﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

namespace LWH
{
    /// <summary>
    /// Defines a class that represents a resizable circular byte queue.
    /// </summary>
    public sealed class CircularByteBuffer
    {
        // Private fields
        private int mReadIndex;
        private int mWriteIndex;
        private int mReadableBytes;
        //private int fSizeUntilCut;
        private byte[] mBuffer;



        /// <summary>
        /// Gets the length of the byte queue
        /// </summary>
        public int ReadableBytes
        {
            get { return mReadableBytes; }
        }

        /// <summary>
        /// Constructs a new instance of a byte queue.
        /// </summary>
        public CircularByteBuffer()
        {
            mBuffer = new byte[2048];
        }

        /// <summary>
        /// Clears the byte queue
        /// </summary>
        internal void Clear()
        {
            mReadIndex = 0;
            mWriteIndex = 0;
            mReadableBytes = 0;
            //fSizeUntilCut = buffer.Length;
        }

        /// <summary>
        /// Clears the byte queue
        /// </summary>
        internal void Clear(int size)
        {
            lock (this)
            {
                if (size > mReadableBytes)
                    size = mReadableBytes;

                if (size == 0)
                    return;

                mReadIndex = (mReadIndex + size) % mBuffer.Length;
                mReadableBytes -= size;

                if (mReadableBytes == 0)
                {
                    mReadIndex = 0;
                    mWriteIndex = 0;
                }

                //fSizeUntilCut = buffer.Length - readIndex;
                return;
            }
        }

        /// <summary>
        /// Extends the capacity of the bytequeue
        /// </summary>
        private void SetCapacity(int capacity)
        {
            byte[] newBuffer = new byte[capacity];

            if (mReadableBytes > 0)
            {
                if (mReadIndex < mWriteIndex)
                {
                    Buffer.BlockCopy(mBuffer, mReadIndex, newBuffer, 0, mReadableBytes);
                }
                else
                {
                    Buffer.BlockCopy(mBuffer, mReadIndex, newBuffer, 0, mBuffer.Length - mReadIndex);
                    Buffer.BlockCopy(mBuffer, 0, newBuffer, mBuffer.Length - mReadIndex, mWriteIndex);
                }
            }

            mReadIndex = 0;
            mWriteIndex = mReadableBytes;
            mBuffer = newBuffer;
        }


        /// <summary>
        /// Enqueues a buffer to the queue and inserts it to a correct position
        /// </summary>
        /// <param name="buffer">Buffer to enqueue</param>
        /// <param name="offset">The zero-based byte offset in the buffer</param>
        /// <param name="size">The number of bytes to enqueue</param>
        public void Enqueue(byte[] buffer, int offset, int size)
        {
            if (size == 0)
                return;

            lock (this)
            {
                if ((mReadableBytes + size) > this.mBuffer.Length)
                    SetCapacity((mReadableBytes + size + 2047) & ~2047);

                if (mReadIndex < mWriteIndex)
                {
                    int rightLength = (this.mBuffer.Length - mWriteIndex);

                    if (rightLength >= size)
                    {
                        Buffer.BlockCopy(buffer, offset, this.mBuffer, mWriteIndex, size);
                    }
                    else
                    {
                        Buffer.BlockCopy(buffer, offset, this.mBuffer, mWriteIndex, rightLength);
                        Buffer.BlockCopy(buffer, offset + rightLength, this.mBuffer, 0, size - rightLength);
                    }
                }
                else
                {
                    Buffer.BlockCopy(buffer, offset, this.mBuffer, mWriteIndex, size);
                }

                mWriteIndex = (mWriteIndex + size) % this.mBuffer.Length;
                mReadableBytes += size;
                //fSizeUntilCut = this.buffer.Length - readIndex;
            }
        }

        /// <summary>
        /// Dequeues a buffer from the queue
        /// </summary>
        /// <param name="buffer">Buffer to enqueue</param>
        /// <param name="offset">The zero-based byte offset in the buffer</param>
        /// <param name="size">The number of bytes to dequeue</param>
        /// <returns>Number of bytes dequeued</returns>
        internal int Dequeue(byte[] buffer, int offset, int size)
        {
            lock (this)
            {
                if (size > mReadableBytes)
                    size = mReadableBytes;

                if (size == 0)
                    return 0;

                if (mReadIndex < mWriteIndex)
                {
                    Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, size);
                }
                else
                {
                    int rightLength = (this.mBuffer.Length - mReadIndex);

                    if (rightLength >= size)
                    {
                        Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, size);
                    }
                    else
                    {
                        Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, rightLength);
                        Buffer.BlockCopy(this.mBuffer, 0, buffer, offset + rightLength, size - rightLength);
                    }
                }

                //size = GetBytes(buffer, offset, size);

                mReadIndex = (mReadIndex + size) % this.mBuffer.Length;
                mReadableBytes -= size;

                if (mReadableBytes == 0)
                {
                    mReadIndex = 0;
                    mWriteIndex = 0;
                }

                //fSizeUntilCut = this.buffer.Length - readIndex;
                return size;
            }
        }




        internal int GetBytes(byte[] buffer, int offset, int size)
        {
            if (size > mReadableBytes)
                size = mReadableBytes;

            if (size == 0)
                return 0;

            if (mReadIndex < mWriteIndex)
            {
                Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, size);
            }
            else
            {
                int rightLength = (this.mBuffer.Length - mReadIndex);

                if (rightLength >= size)
                {
                    Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, size);
                }
                else
                {
                    Buffer.BlockCopy(this.mBuffer, mReadIndex, buffer, offset, rightLength);
                    Buffer.BlockCopy(this.mBuffer, 0, buffer, offset + rightLength, size - rightLength);
                }
            }

            return size;
        }

        public byte[] GetBytes(int offset, int size)
        {
            


            if (offset+size > mReadableBytes||size==0)
            {
                return new byte[0];
            }


            byte[] getBytes = new byte[size];

            int getIndex = (mReadIndex + offset) % this.mBuffer.Length;

            if (getIndex < mWriteIndex)
            {
                Buffer.BlockCopy(this.mBuffer, getIndex, getBytes, 0, size);
            }
            else
            {
                int rightLength = (this.mBuffer.Length - getIndex);

                if (rightLength >= size)
                {
                    Buffer.BlockCopy(this.mBuffer, getIndex, getBytes, 0, size);
                }
                else
                {
                    Buffer.BlockCopy(this.mBuffer, getIndex, getBytes, 0, rightLength);
                    Buffer.BlockCopy(this.mBuffer, 0, getBytes, rightLength, size - rightLength);
                }
            }

            return getBytes;
        }

        public byte[] Dequeue(int size)
        {


            if (size <= 0 || size > mReadableBytes)
            {
                return new byte[0];

            }

            byte[] getBytes = new byte[size];

            if (mReadIndex < mWriteIndex)
                {
                    Buffer.BlockCopy(this.mBuffer, mReadIndex, getBytes, 0, size);
                }
                else
                {
                    int rightLength = (this.mBuffer.Length - mReadIndex);

                    if (rightLength >= size)
                    {
                        Buffer.BlockCopy(this.mBuffer, mReadIndex, getBytes, 0, size);
                    }
                    else
                    {
                        Buffer.BlockCopy(this.mBuffer, mReadIndex, getBytes, 0, rightLength);
                        Buffer.BlockCopy(this.mBuffer, 0, getBytes,  rightLength, size - rightLength);
                    }
                }

                //size = GetBytes(buffer, offset, size);

                mReadIndex = (mReadIndex + size) % this.mBuffer.Length;
                mReadableBytes -= size;

                if (mReadableBytes == 0)
                {
                    mReadIndex = 0;
                    mWriteIndex = 0;
                }

                //fSizeUntilCut = this.buffer.Length - readIndex;
                return getBytes;
            
        }


        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int memcmp(byte[] b1, byte[] b2, long count);

        public bool CompareBuffer(byte[] buffer)
        {
            int size = buffer.Length;

            if (size > mReadableBytes|| size == 0)
            {
                return false;
            }

            byte[] checkBuffer;

            if (mReadIndex < mWriteIndex)
            {
                //return memcmp(mBuffer, buffer, size)==0;
                checkBuffer = mBuffer;
            }
            else
            {
                int rightLength = (this.mBuffer.Length - mReadIndex);

                if (rightLength >= size)
                {
                    checkBuffer = mBuffer;
                }
                else
                {
                    var getBuffer = new byte[size];
                    
                    Buffer.BlockCopy(this.mBuffer, mReadIndex, getBuffer, 0, rightLength);
                    Buffer.BlockCopy(this.mBuffer, 0, buffer, rightLength, size - rightLength);

                    checkBuffer = getBuffer;
                }
            }

            return memcmp(checkBuffer, buffer, size) == 0;
        }






    }

}
